/*
Copyright (C) 2022-2024 Inspur Corp.

SPDX-License-Identifier: Apache-2.0
*/
package client

import (
	"encoding/hex"
	"encoding/json"
	"time"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/client/chain"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/client/iam"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/client/ledger"
	config2 "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/core/config"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain_sdk"
	iam2 "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/iam"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"

	"icep/chain/artifact"
	"icep/chain/constant"
	"icep/chain/model"
	constant2 "icep/common/constant"
	"icep/common/logger"
	"icep/common/proto"
)

var log = logger.GetLogger("chain")

type IchainClient struct {
	Config
	sdk    *ichain_sdk.IChainSDK
	chain  *chain.Client
	ledger *ledger.Client
}

type Config struct {
	TenantId       string
	OrgName        string
	ServiceName    string
	ChainName      string
	ConnectionFile []byte
	Orgs           []artifact.Org
	Consensers     []artifact.Node
	Nodes          []string
	Cert           []byte
	PrvKey         []byte
	ChainLog       string
	Alg            string
	HashAlg        string
}

func NewClient(c Config) *IchainClient {
	return &IchainClient{
		Config: c,
	}
}

func (client *IchainClient) Initialize() error {
	cc := artifact.NewConnectConfig(client.ServiceName, client.ChainName, client.Alg, client.HashAlg, client.ChainLog)
	connectBytes := cc.SetOrgs(client.Orgs).SetConsensers(client.Consensers).Build()
	log.Debugf("connect artifact: %s", string(connectBytes))
	client.ConnectionFile = connectBytes
	sdk, err := ichain_sdk.New(config2.FromRaw(connectBytes, "yaml"))
	if err != nil {
		log.Errorf("initialize ichain client, new sdk error: %v", err)
		return err
	}
	client.sdk = sdk

	iamInstance, err := iam.New(sdk.Context(), iam.WithOrg(client.OrgName))
	if err != nil {
		log.Errorf("initialize ichain client, new iam client error: %v", err)
		return err
	}
	identity, err := iamInstance.CreateSigningIdentity(iam2.WithCert(client.Cert), iam2.WithPrivateKey(client.PrvKey))
	if err != nil {
		log.Errorf("initialize ichain client, create identity error: %v", err)
		return err
	}

	chainInstance, err := chain.New(sdk.ChainContext(client.ChainName, ichain_sdk.WithIdentity(identity)))
	if err != nil {
		log.Errorf("initialize ichain client, new chain client error: %v", err)
		return err
	}
	client.chain = chainInstance

	ledgerInstance, err := ledger.New(sdk.ChainContext(client.ChainName, ichain_sdk.WithIdentity(identity)))
	if err != nil {
		log.Errorf("initialize ichain client, new ledger client error: %v", err)
		return err
	}
	client.ledger = ledgerInstance
	return nil
}

// 数据存证
func (client *IchainClient) PutDataEvidence(key, value string) (txId, payload string) {
	response, err := client.executeContract(constant.ContractNameDataEvidence, "setData", key, value)
	if err != nil {
		log.Errorf("put data evidence error: %v", err)
		txId = ""
		payload = ""
		return
	}
	txId = string(response.TransactionID)
	payload = string(response.Payload)
	return
}

// 查询数据存证信息
func (client *IchainClient) GetDataEvidence(key string) string {
	response, err := client.queryContract(constant.ContractNameDataEvidence, "getData", key)
	if err != nil {
		log.Errorf("get data evidence error: %v", err)
		return ""
	}
	return string(response.Payload)
}

// 获取数据存证历史
func (client *IchainClient) GetDataHistory(key string) string {
	response, err := client.queryContract(constant.ContractNameDataEvidence, "getHistory", key)
	if err != nil {
		log.Errorf("get data history error: %v", err)
		return ""
	}
	return string(response.Payload)
}

// 文件存证
func (client *IchainClient) PutFileEvidence(key, value string) (string, string) {
	response, err := client.executeContract(constant.ContractNameFileEvidence, "setData", key, value)
	if err != nil {
		log.Errorf("put file evidence error: %v", err)
		return "", ""
	}
	return string(response.TransactionID), string(response.Payload)
}

// 查询文件存证信息
func (client *IchainClient) GetFileEvidence(key string) string {
	response, err := client.queryContract(constant.ContractNameFileEvidence, "getData", key)
	if err != nil {
		log.Errorf("get file evidence error: %v", err)
		return ""
	}
	return string(response.Payload)
}

// 获取文件存证历史
func (client *IchainClient) GetFileHistory(key string) string {
	response, err := client.queryContract(constant.ContractNameFileEvidence, "getHistory", key)
	if err != nil {
		log.Errorf("get file history error: %v", err)
		return ""
	}
	return string(response.Payload)
}

// 取证信息上链
func (client *IchainClient) PutObtainEvidence(key, value string) (string, string) {
	response, err := client.executeContract(constant.ContractNameObtainEvidence, "setData", key, value)
	if err != nil {
		log.Errorf("put obtain evidence error: %v", err)
		return "", ""
	}
	return string(response.TransactionID), string(response.Payload)
}

// 查询取证信息
func (client *IchainClient) GetObtainEvidence(key string) string {
	response, err := client.queryContract(constant.ContractNameObtainEvidence, "getData", key)
	if err != nil {
		log.Errorf("get obtain evidence error: %v", err)
		return ""
	}
	return string(response.Payload)
}

// 获取取证历史
func (client *IchainClient) GetObtainHistory(key string) string {
	response, err := client.queryContract(constant.ContractNameObtainEvidence, "getHistory", key)
	if err != nil {
		log.Errorf("get obtain history error: %v", err)
		return ""
	}
	return string(response.Payload)
}

// 根据交易id获取区块信息（区块号、区块哈希、前哈希）
func (client *IchainClient) GetBlockInfoById(txId string) *model.BlockInfo {
	block, err := client.ledger.QueryBlockByTxID(ichain.TransactionID(txId))
	if err != nil {
		log.Errorf("get block info by txid error: %v", err)
		return nil
	}
	blockHash := hex.EncodeToString(block.GetHeader().GetDataHash())
	preHash := hex.EncodeToString(block.GetHeader().GetPreviousHash())
	return &model.BlockInfo{
		BlockNum:  block.GetHeader().GetNumber(),
		BlockHash: blockHash,
		PreHash:   preHash,
	}
}

// 根据交易id获取交易信息（交易时间、交易内容）
func (client *IchainClient) GetTransactionInfoById(txId string) *model.TransactionInfo {
	tx, err := client.ledger.QueryTransaction(ichain.TransactionID(txId))
	if err != nil {
		log.Errorf("get transaction info by txid error: %v", err)
		return nil
	}

	payload, err := proto.GetPayload(tx.GetTransactionEnvelope())
	if err != nil {
		log.Errorf("get transaction info by txid, get payload error: %v", err)
		return nil
	}
	chainHeader, err := proto.GetChainHeader(payload.GetHeader().GetChainHeader())
	if err != nil {
		log.Errorf("get transaction info by txid, get ChainHeader error: %v", err)
		return nil
	}

	location, _ := time.LoadLocation("Asia/Shanghai")
	txTime := chainHeader.Timestamp.AsTime()
	txTimeStr := txTime.In(location).Format(constant2.TimeLayout)

	transaction, err := proto.GetTransaction(payload.GetData())
	if err != nil {
		log.Errorf("get transaction info by txid, get transaction error: %v", err)
		return nil
	}

	kvWrite, err := proto.GetWriteSetFromTransaction(transaction)
	if err != nil {
		log.Errorf("get transaction info by txid, get write set error: %v", err)
		return nil
	}
	writeModel := model.WriteModel{
		Key:      kvWrite.Key,
		Value:    string(kvWrite.Value),
		IsDelete: kvWrite.IsDelete,
	}
	content, _ := json.Marshal(writeModel)
	return &model.TransactionInfo{
		TxId:      txId,
		TxTime:    txTimeStr,
		TxContent: string(content),
	}

}

func (client *IchainClient) queryContract(contractName, fcn string, params ...string) (chain.Response, error) {
	args := make([][]byte, len(params))
	for i, param := range params {
		args[i] = []byte(param)
	}
	req := chain.Request{
		ContractID: contractName,
		Fcn:         fcn,
		Args:        args,
	}

	response, err := client.chain.Query(req, chain.WithTargetEndpoints(client.Nodes...))
	return response, err
}

func (client *IchainClient) executeContract(contractName, fcn string, params ...string) (chain.Response, error) {
	args := make([][]byte, len(params))
	for i, param := range params {
		args[i] = []byte(param)
	}
	req := chain.Request{
		ContractID: contractName,
		Fcn:         fcn,
		Args:        args,
	}
	response, err := client.chain.Execute(req, chain.WithTargetEndpoints(client.Nodes...))
	return response, err
}
